#pragma once
#include <iostream>
#include <sys/select.h>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>

using namespace std;

class TcpServer
{
public:
    const static int N = 1024;
    TcpServer(uint16_t port)
        :_port(port), _listensock(-1)
    {
        // 创建tcp套接字
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensock < 0)
        {
            cout << "socket error" << endl;
            exit(1);
        }
        cout << "create socket success, _listensock: " << _listensock << endl;
        // 填写结构体信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        // 绑定IP和PORT
        int n = bind(_listensock, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            cout << "bind error" << endl;
            exit(2);
        }
        cout << "bind success, _listensock: " << _listensock << endl;
        // 监听
        n = listen(_listensock, 16);
        if(n < 0)
        {
            cout << "listen error" << endl;
            exit(2);
        }
        cout << "listen success, _listensock: " << _listensock << endl;

        // 初始化数组, 0是_listensock
        for(int i=0; i<N; i++)
        {
            _arr_fd[i] = -1;
        }
        _arr_fd[0] = _listensock;
    }

    void AcceptClient()
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sockfd = accept(_listensock, (struct sockaddr*)&peer, &len);
        if(sockfd < 0) return;
        // 用数组保存好新的fd
        int pos = 1;
        for(; pos < N; pos++)
        {
            // 有空出来的，可以给sockfd占用
            if(_arr_fd[pos] == -1) break;
        }
        if(pos == N)
        {
            // 有可能数组已经满了
            close(sockfd);
            cout << "_arr_fd is full" << endl;
        }
        else
        {
            // sockfd添加到数组
            _arr_fd[pos] = sockfd;
            cout << "sockfd add success, sockfd: " << sockfd << endl;
        }
    }

    void Service(int pos)
    {
        char buffer[1024];
        ssize_t n = recv(_arr_fd[pos], buffer, sizeof(buffer)-1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            cout << "client msg: " << buffer << endl;
            string str = "[echo msg]# ";
            str += buffer;
            send(_arr_fd[pos], str.c_str(), str.size(), 0);
        } 
        else if(n == 0)
        {
            cout << "recv quit" << endl;
            // 读完 数组重新设置为-1，关闭fd
            close(_arr_fd[pos]);
            _arr_fd[pos] = -1;
        }
        else 
        {
            cout << "recv error" << endl;
            // 读失败 数组重新设置为-1，关闭fd
            close(_arr_fd[pos]);
            _arr_fd[pos] = -1;
        }
    }

    // 处理fd就是接收新连接(accept), _listensock已经就绪
    void handler(fd_set& rfd)
    {
        // 不一定只有一个fd(_listensock), 可能有多个
        for(int i=0; i<N; i++)
        {
            // 没有被设置的要跳过
            if(_arr_fd[i] == -1) continue;
            // 判断是否就绪，1: 就绪  0: 没有就绪
            if(FD_ISSET(_arr_fd[i], &rfd))
            {
                // 不确定是_listensock还是其他的fd(连接的fd), 所以要区分
                if(_arr_fd[i] == _listensock)
                {
                    AcceptClient();
                }
                else 
                {
                    Service(i);// 正常通信
                }
            }
            
        }
    }
    void Loop()
    {
        while(true)
        {
            // 读事件
            fd_set rfd;// 文件描述符集
            FD_ZERO(&rfd);// 清空
            // max_fd就是select遍历的范围，左开右闭
            int max_fd = _arr_fd[0];
            for(int i=0; i<N; i++)
            {
                if(_arr_fd[i] == -1) continue;// 没有在数组里面，说明没有被设置
                // 设置进去, select就要对该fd关心, 但是未必就绪
                FD_SET(_arr_fd[i], &rfd);
                // 找最大的fd
                if(_arr_fd[i] > max_fd) max_fd = _arr_fd[i];
            }

            // 等待多个合理的(已经设置好的)文件描述符
            int n = select(max_fd+1, &rfd, nullptr, nullptr, nullptr);
            if(n > 0)
            {
                cout << "处理就绪fd" << endl;
                handler(rfd);
            }
            else if(n == 0)
            {
                cout << "select timeout" << endl;
            }
            else 
            {
                cout << "select error" << endl;
            }
        }
    }
    ~TcpServer()
    {
        if (_listensock > -1)
            close(_listensock);
    }

private:
    uint16_t _port;
    int _listensock;

    // 用一个数组管理fd，否则每次fd都要重置
    int _arr_fd[N];
};